package jmpo.ex;

import java.util.InputMismatchException;
import java.util.Scanner;

/**
 * Иногда вам необходимо выполнить некоторые операции над данными вне зависимости от того,
 * возникло ли исключение. Чаще всего это связано с необходимостью занимать и освобождать 
 * ресурсы. Например при пработа с файлами необходимо закрывать поток. Не важно была ли
 * ошибка или нет, но поток нужно закрыть. 
 * Если мы используем стратегию перевыброса исключений, то такая задача только с блоками 
 * try / catch может быть крайне неудобной из-за колоссального дублирования кода.
 * Эту проблему решает блок finally, который должен быть расположен после всех catch.
 * Он будет исполнен вне зависимости от того было ли исключений или не было, но после 
 * всего кода try / catch и до передачи управления.
 */
final class FinallyExample {
    static void test() {
        rethrow("abc"); // вне зависимости от строки будет надпись "the Scanner is closed"
    }

    static void rethrow(String s) {
        /**
         * Мы объявляем сслыку на Scanner до блок try, чтобы она была 
         * доступна и в блоке finally
         */
        Scanner sc = null;
        try {
            sc = new Scanner(s);
            var i = sc.nextInt();
            System.out.format("Next number is: %d\n", i + 1);
        } catch(InputMismatchException e) {
            System.out.println("not a number in the input stream");
            throw e;
        } finally {
            sc.close(); // выполняем необходимые дейсвия
            System.out.println("the Scanner is closed");
        }
    }
}

/**
 * Есть случаи, когда блок finally не выполнится:
 *     1) JVM будет завершена через System.exit(exitStatus) / Runtime.getRuntime().halt(exitStatus) или разрушится;
 *     2) поток, в котором происходит работа, будет завершён через stop();
 *     3) если программа зависнет в блоке try (следовательно будет остановлена нештатно см. 1)
 *     4) если поток является "демоном", то при штатном завершении программы он будет немедленно завершён (см. 2). 
 * Это накладывает определённую отвественность, если вы управляете ресурсами, которые не будут автоматически 
 * возвращены при завершении программы.
 */
/**
 * Ещё одна опасность при работе с блоками finally - это исключения в самом блоке finally.
 * Если необходимо закрыть два ресурса и при закрытии возникает исключение, то программа 
 * прекращает работу и аварийно завершается с этим исключением.
 * Опишем это более формамально:
 *    1) если блок try выполняется до конца и блок finally выполняется до конца, то исключения не происходит;
 *    2) если блок try обрывается исключением R и блок finaly выполняется до конца, то программа выбрасывает R;
 *    3) если блок try выполняется до конца, но блок finally выбрасывает исключение S, то программа прерывается в блоке finally и выбрасывает S;
 *    4) если блок try обрывается исключением R и блок final выбрасывает исключение S, то исключение R отбрасывается и программа выбрасывает исключение S.
 * 
 * 4 пункт особенно неприятен. В этой ситуации часть ресурсов будет потеряна, как и в пункте 3, но теперь мы ещё и теряем исключение из блока try.
 */

 /**
  * Хотя блоки finally помогают справиться с поставленной задачей,
  * они требуют некоторого сервисного кода и дополнительной работы 
  * с переменными (например нам пришлось вынести объявление ссылки из try).
  * В Java 7 появилась и в Java 9 была улучшена конструкция try-with-resources.
  * Она позволяет автоматически вызывать метод close на объектах, 
  * ссылке на котороые указаны в описании try.
  */
  final class TryWithResourcesExample {
    static void test() {
        rethrow("abc"); // вне зависимости от строки будет надпись "the Scanner is closed"
    }

    static void rethrow(String s) {
        try (var sc = new Scanner(s)){
            var i = sc.nextInt();
            System.out.format("Next number is: %d\n", i + 1);
        } catch(InputMismatchException e) {
            System.out.println("not a number in the input stream");
            throw e;
        }
    }
}

/**
 * Помимо упрощения кода try-with-resources позволяет решить проблему с закрытием нескольких ресурсов.
 * Исключения, которые будут возникать при вызове методов close для освобождения ресурсов, будут <<подавлены>> (suppressed).
 * Таким образом для каждого ресурса его метод close гарантирован будет вызван, за исключением ситуаций, когда блок finally 
 * принципиально не может быть вызван (см. выше).
 * 
 * Опишем это более формамально:
 *    1) если блок try выполняется до конца и вызовы close() выполняются до конца, то исключения не происходит;
 *    2) если блок try обрывается исключением R и вызовы close() выполняются до конца, то программа выбрасывает R;
 *    3) если блок try выполняется до конца, но при вызовах close() происходит ряд исключений S1 .. Sn, то программа 
 *           завершается исключением S1, причём остальные исключения попадают в список подавленных.
 *    4) если блок try обрывается исключением R и при вызовах close() происходит ряд исключений S1 .. Sn, то 
 *           программа завершается искючением R, а исключениям S1 ... Sn попадают в список подавленных.
 * 
 * Начиная с Java 7 есть возможность выбрать все подавленные исключения с помощью метода Throwable[] Throwable.getSuppressed().
 */

/**
 * Для того, чтобы наш собственный класс мог реализовывать автоматическое управление ресурсами,
 * нужно реализовать интерфейс AutoClosable, содержащий один единственный метод close(), который
 * ничего не принимает и не возвращает, то может выбрасывать исключение типа Exception.
 */
final class Resource implements AutoCloseable {
    @Override
    public void close() {
        System.out.format("Resource %s is closing...\n", name);
        throw new RuntimeException(String.format("Exception from the %s", name));
    }

    Resource(String name) {
        this.name = name;
    }

    private String name;

    static void throwing() {
        try (Resource r1 = new Resource("A"); Resource r2 = new Resource("B")) {
            throw new RuntimeException("Exception from the try block");
        }
    }

    static void test() {
        try {
            throwing();
        } catch(Exception e) {
            System.out.println(e.getMessage());
            for (var es : e.getSuppressed())
                System.out.println(es.getMessage());
        }
    }
}

/**
 * От интерфейса AutoClosable унаследован библиотечный интерфейса Closable.
 * Closable существовал в java.io и до try-with-resources. Он используется 
 * только библиотечными классами java.io. Метод close интерфейса Closable 
 * выбрасывает исключение IOException. 
 * Соглашение о пользовательских классах гласит, что вам следует реализоваывать
 * интерфейса AutoClosable или наследовать свои интерфейсы со специфическими 
 * исключениями от него, включая случаи, когда вы выкидываете IOException.
 */

public class ExceptionFinally {
    public static void main(String[] args) {
        if (false) FinallyExample.test();
        if (false) TryWithResourcesExample.test();
        if (false) Resource.test();
    }
}
